<?php
/**
 * XGPHP 轻量级PHP框架
 * @link http://xgphp.xg3.cn
 * @version 2.0.0
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @author 讯高科技 <xungaokeji@qq.com>
*/
namespace xg;
use \helper\Arr;
use \helper\File;

// 容器管理类
class Container {
    protected $bindings = [];
    protected $instances = [];
    protected $nameMap = [];
    protected $classMap = [];
	
	// 自动加载
	public function __construct() {
		spl_autoload_register([$this, 'autoload'],false,true);
	}
	
	// 自动加载
	public function autoload(string $class) {
		$arr = explode('\\',$class);
		$first = array_shift($arr);
		$_class = implode(XG_DS,$arr);
		
		// 查找应用文件
		if($first == 'app') {
			$phpfile = File::path('xg:app',$_class.'.php');
			if(is_file($phpfile)) {
				require_once $phpfile;
				return true;
			}
		}elseif($first == 'apps') {
			$phpfile = File::path('xg:apps',$_class.'.php');
			if(is_file($phpfile)) {
				require_once $phpfile;
				return true;
			}
		}
		
		// 查找类文件映射
		if($this->classMap[$class] and is_file(File::path($phpfile=$this->classMap[$class]))) {
			require_once $phpfile;
			return true;
		}
		return false;
	}
	
    /**
     * 绑定一个实例到容器中
     * @param string $abstract 抽象名称
     * @param mixed $concrete 实例或回调函数
     * @return void
     */
    public function bind($abstract, $concrete) {
		$this->bindings[$abstract] = $concrete;
    }

    /**
     * 批量绑定实例到容器中
     * @param array $bindings 包含抽象名称和实例或回调函数的关联数组
     * @return void
     */
    public function batchBind(array $bindings) {
        foreach ($bindings as $abstract => $concrete) {
            if(is_numeric($abstract)) {
                $abstract=$concrete;
            }
            $this->bind($abstract, $concrete);
        }
    }
	
	// 指定类文件
	public function classFile(string $class,string $phpfile) {
		// 规范化类名
		$class=trim(str_replace('\\',XG_DS,$class),XG_DS);
		$this->classMap[$class]=$phpfile;
	}

	// 批量指定类文件
	public function classMap(array $map) {
		foreach($map as $class=>$phpfile) {
			$this->classFile($class,$phpfile);
		}
	}

    /**
     * 设置容器中某个实例的别名
     * @param string $name 别名
     * @param string $abstract 抽象名称
     * @return void
    */
    public function nameMap($name,$abstract=null) {
        if(is_array($name)) {
            $this->nameMap=Arr::extend($this->nameMap,$name);
        }elseif($abstract) {
            $this->nameMap[$name]=$abstract;
        }
    }

    /**
     * 获取容器中某个实例
     * @param string $abstract 抽象名称
     * @param array $parameters 参数数组
     * @return mixed 解析出的实例
     */
    public function get($abstract, $parameters = null) {
        if($this->nameMap[$abstract]) {
            $abstract=$this->nameMap[$abstract];
        }
        if(isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }
		return $this->make($abstract, $parameters);
    }

    /**
     * 从容器中解析出一个实例
     * @param string $abstract 抽象名称
     * @param array $parameters 参数数组
     * @return mixed 解析出的实例
     */
    public function make($abstract, $parameters = null) {
		if($abstract=='app') {
			return $this;
		}
        if (isset($this->bindings[$abstract])) {
            $concrete = $this->bindings[$abstract];
            if ($concrete instanceof \Closure) {
                $concrete=$concrete->bindTo($this);
                return $this->instances[$abstract]=$concrete(...$parameters);
            } else {
                if(is_object($concrete)) {
                    return $this->instances[$abstract]=$concrete;
                }
                $object = new $concrete($parameters);
                $this->instances[$abstract] = $object;
                return $object;
            }
        }
        throw new Exception(\xg\facade\Lang::get("no binding for {:abstract}",['abstract'=>$abstract]));
    }

    public function set($abstract,$instance) {
        $this->instances[$abstract] = $instance;
        return $this;
    }

    /**
     * 判断容器中是否存在某个实例
     * @param string $abstract 抽象名称
     * @return bool 是否存在
     */
    public function has($abstract) {
		return isset($this->classMap[$abstract]) or isset($this->bindings[$abstract]) or isset($this->instances[$abstract]);
    }

    /**
     * 从容器中移除一个实例
     * @param string $abstract 抽象名称
     * @return void
     */
    public function remove($abstract) {
        unset($this->bindings[$abstract]);
    }
}
?>